// itk include
#include "itkNeighborhoodIterator.h"
#include "itkConstantBoundaryCondition.h"

#include "itkFlipImageFilter.h"
#include "itkCastImageFilter.h"
#include "itkImageFileReader.h"
#include "itkImageFileWriter.h"
#include "itkImageRegionIteratorWithIndex.h"
#include "itkResampleImageFilter.h"
#include "itkCropImageFilter.h"
#include "itkLinearInterpolateImageFunction.h"
#include "itkNearestNeighborInterpolateImageFunction.h"
#include "itkBinaryThresholdImageFilter.h"

#include "DataTypeDefine.h"

class BaseAlgorithm
{
public:
	template<typename Tin, typename Tout>
	static typename Tout::Pointer CastImage(typename Tin::Pointer input);

	template<typename Tin>
	static typename Tin::Pointer DuplicateImage(typename Tin::Pointer input);

	// Load image file eg. nii.gz
	template<typename Tin>
	static typename Tin::Pointer LoadImageFile(std::string fileName);

	// Load image file eg. mhd
	template<typename Tin>
	static typename Tin::Pointer LoadMetaImage(std::string fileName);

	// Output image file eg. nii.gz
	template<class Tin>
	static void SaveImageFile(typename Tin::Pointer img, std::string fileName);

	// Compute skeleton using thinning based method
	static LabelMapPointer skeletonize(LabelMapPointer inputImage);

	// Resample image 
	template<typename Tin>
	static typename Tin::Pointer ResampleImage(typename Tin::Pointer input, typename Tin::SpacingType spacing);

	// Crop image 
	template<typename Tin, typename Tout>
	static typename Tout::Pointer CropImage(typename Tin::Pointer input, typename Tin::SizeType lowerBound, typename Tin::SizeType upperBound);

	// Binary Threshold image
	template<typename Tin>
	static LabelMapPointer ThresholdImage(typename Tin::Pointer input, typename Tin::PixelType lower, typename Tin::PixelType upper, LabelMapType::PixelType insideValue, LabelMapType::PixelType outsideValue = 0);

	// Binary Threshold image
	template<typename Tin>
	static typename Tin::Pointer FlipImage(typename Tin::Pointer input, int axis);

};

//-----------------------------------------------------------------------------
template<typename Tin, typename Tout>
typename Tout::Pointer BaseAlgorithm::CastImage(typename Tin::Pointer input)
{
	typedef itk::CastImageFilter<Tin, Tout> castImageFilterType;
	typename castImageFilterType::Pointer castImageFilter = castImageFilterType::New();

	castImageFilter->SetInput(input);

	try
	{
		castImageFilter->Update();
	}
	catch (itk::ExceptionObject& err)
	{
		std::cerr << "err " << err << std::endl;
	}

	return castImageFilter->GetOutput();
}

//-----------------------------------------------------------------------------
template<typename Tin>
typename Tin::Pointer BaseAlgorithm::DuplicateImage(typename Tin::Pointer input)
{
	return BaseAlgorithm::castImage<Tin, Tin>(input);
}

//-----------------------------------------------------------------------------
template<typename Tin>
typename Tin::Pointer BaseAlgorithm::LoadMetaImage(std::string fileName)
{
	typedef itk::ImageFileReader<Tin> ReaderType;
	typename ReaderType::Pointer reader = ReaderType::New();
	MetaIOType::Pointer MetaIO = MetaIOType::New();

	reader->SetFileName(fileName);
	reader->SetImageIO(MetaIO);

	try 
	{
		reader->Update();
	}
	catch 
		(itk::ExceptionObject& err) {
		std::cerr << "err " << err << std::endl;
	}

	return reader->GetOutput();
}

//-----------------------------------------------------------------------------
template<typename Tin>
typename Tin::Pointer BaseAlgorithm::LoadImageFile(std::string fileName)
{
	typedef itk::ImageFileReader<Tin> ReaderType;
	typename ReaderType::Pointer reader = ReaderType::New();
	
	std::string suffix = fileName.substr(fileName.find_last_of('.') + 1);
	if (suffix == "gz" || suffix == "nii")
	{
		NiftiImageIOType::Pointer niftiIO = NiftiImageIOType::New();
		reader->SetImageIO(niftiIO);
	}
	else if (suffix == "mhd")
	{
		MetaIOType::Pointer MetaIO = MetaIOType::New();
		reader->SetImageIO(MetaIO);
	}
	else if (suffix == "nrrd" || suffix == "nhdr")
	{
		NrrdImageIOType::Pointer nrrdIO = NrrdImageIOType::New();
		reader->SetImageIO(nrrdIO);
	}

	reader->SetFileName(fileName);
	
	try
	{
		reader->Update();
	}
	catch
		(itk::ExceptionObject& err) {
		std::cerr << "Error reading " << fileName << std::endl;
	}

	return reader->GetOutput();
}

//-----------------------------------------------------------------------------
template<typename Tin>
void BaseAlgorithm::SaveImageFile(typename Tin::Pointer img, std::string fileName)
{
	typedef itk::ImageFileWriter<Tin> ImageWriterType;
	typename ImageWriterType::Pointer writer = ImageWriterType::New();

	std::string suffix = fileName.substr(fileName.find_last_of('.') + 1);
	if (suffix == "gz" || suffix == "nii")
	{
		NiftiImageIOType::Pointer niftiIO = NiftiImageIOType::New();
		writer->SetImageIO(niftiIO);
	}
	else if (suffix == "mhd")
	{
		MetaIOType::Pointer MetaIO = MetaIOType::New();
		writer->SetImageIO(MetaIO);
	}
	else if (suffix == "nrrd" || suffix == "nhdr")
	{
		NrrdImageIOType::Pointer nrrdIO = NrrdImageIOType::New();
		writer->SetImageIO(nrrdIO);
	}

	writer->SetInput(img);
	writer->SetFileName(fileName);
	writer->SetUseCompression(true);

	try 
	{
		writer->Update();
	}
	catch (itk::ExceptionObject& err) 
	{
		std::cerr << "err " << err << std::endl;
	}
}

//-----------------------------------------------------------------------------
LabelMapPointer BaseAlgorithm::skeletonize(LabelMapPointer inputImage)
{
	typedef itk::ConstantBoundaryCondition<LabelMapType> BoundaryConditionType;
	typedef itk::NeighborhoodIterator<LabelMapType, BoundaryConditionType> NeighborhoodIteratorType;
	typedef NeighborhoodIteratorType::NeighborhoodType NeighborhoodType;

	NeighborhoodIteratorType::RadiusType radius;
	radius.Fill(1);

	NeighborhoodIteratorType ot(radius, inputImage, inputImage->GetLargestPossibleRegion());

	BoundaryConditionType boundaryCondition;
	boundaryCondition.SetConstant(0);
	ot.SetBoundaryCondition(boundaryCondition);

	typedef NeighborhoodIteratorType::OffsetType OffsetType;
	OffsetType N = { { 0, -1, 0 } }; // north
	OffsetType S = { { 0, 1, 0 } };  // south
	OffsetType E = { { 1, 0, 0 } };  // east
	OffsetType W = { { -1, 0, 0 } }; // west
	OffsetType U = { { 0, 0, 1 } };  // up
	OffsetType B = { { 0, 0, -1 } }; // bottom

}

//-----------------------------------------------------------------------------
template<typename Tin>
typename Tin::Pointer BaseAlgorithm::ResampleImage(typename Tin::Pointer input, typename Tin::SpacingType outputSpacing)
{
	typedef itk::ResampleImageFilter<Tin, Tin> ResampleImageFilterType;
	typename ResampleImageFilterType::Pointer resampleFilter = ResampleImageFilterType::New();

	typedef itk::NearestNeighborInterpolateImageFunction<Tin, double> LinearInterpolatorType;
	typename LinearInterpolatorType::Pointer interpolator = LinearInterpolatorType::New();


	typename Tin::SizeType inputSize = input->GetLargestPossibleRegion().GetSize();
	typename Tin::SpacingType inputSpacing = input->GetSpacing();
	typename Tin::SizeType outputSize;
	typedef typename Tin::SizeType::SizeValueType SizeValueType;
	outputSize[0] = static_cast<SizeValueType>(inputSize[0] * inputSpacing[0] / outputSpacing[0] + .5);
	outputSize[1] = static_cast<SizeValueType>(inputSize[1] * inputSpacing[1] / outputSpacing[1] + .5);
	outputSize[2] = static_cast<SizeValueType>(inputSize[2] * inputSpacing[2] / outputSpacing[2] + .5);

	resampleFilter->SetInput(input);
	resampleFilter->SetInterpolator(interpolator);
	resampleFilter->SetSize(outputSize);
	resampleFilter->SetOutputSpacing(outputSpacing);
	resampleFilter->SetOutputOrigin(input->GetOrigin());
	resampleFilter->SetOutputDirection(input->GetDirection());

	try
	{
		resampleFilter->Update();
	}
	catch
		(itk::ExceptionObject& err) {
		std::cerr << "err " << err << std::endl;
	}

	return resampleFilter->GetOutput();
}

//-----------------------------------------------------------------------------
template<typename Tin, typename Tout>
typename Tout::Pointer BaseAlgorithm::CropImage(typename Tin::Pointer input, typename Tin::SizeType lowerBound, typename Tin::SizeType upperBound)
{
	typedef typename itk::CropImageFilter <Tin, Tout> CropImageFilterType;
	typename CropImageFilterType::Pointer cropFilter = CropImageFilterType::New();

	cropFilter->SetInput(input);
	cropFilter->SetLowerBoundaryCropSize(lowerBound);
	cropFilter->SetUpperBoundaryCropSize(upperBound);

	try
	{
		cropFilter->Update();
	}
	catch (itk::ExceptionObject& err) 
	{
		std::cerr << "err " << err << std::endl;
	}

	return cropFilter->GetOutput();
}

//-----------------------------------------------------------------------------
template<typename Tin>
LabelMapPointer BaseAlgorithm::ThresholdImage(typename Tin::Pointer input, typename Tin::PixelType lower, typename Tin::PixelType upper, LabelMapType::PixelType insideValue, LabelMapType::PixelType outsideValue)
{
	typedef typename itk::BinaryThresholdImageFilter <Tin, LabelMapType> BinaryThresholdFilterType;
	typename BinaryThresholdFilterType::Pointer thresholdFilter = BinaryThresholdFilterType::New();

	thresholdFilter->SetLowerThreshold(lower);
	thresholdFilter->SetUpperThreshold(upper);
	thresholdFilter->SetInsideValue(insideValue);
	thresholdFilter->SetOutsideValue(outsideValue);
	thresholdFilter->SetInput(input);

	try
	{
		thresholdFilter->Update();
	}
	catch (itk::ExceptionObject& err) 
	{
		std::cerr << "err " << err << std::endl;
	}

	return thresholdFilter->GetOutput();
}

template<typename Tin>
typename Tin::Pointer BaseAlgorithm::FlipImage(typename Tin::Pointer input, int axis)
{
	typedef typename itk::FlipImageFilter<Tin> FlipImageFilterType;
	typename FlipImageFilterType::Pointer flipImageFilter = FlipImageFilterType::New();

	typename FlipImageFilterType::FlipAxesArrayType flipAxes;
	flipAxes[0] = false;
	flipAxes[1] = false;
	flipAxes[2] = false;
	flipAxes[axis] = true;

	flipImageFilter->SetInput(input);
	flipImageFilter->SetFlipAxes(flipAxes);

	try
	{
		flipImageFilter->Update();
	}
	catch (itk::ExceptionObject& err)
	{
		std::cerr << "err " << err << std::endl;
	}

	return flipImageFilter->GetOutput();
}